#include <dlfcn.h>
#include <Ecore.h>
#include <Ecore_Str.h>
#include "e.h"
#include "e_mod_main.h"
#include "compiz.h"
#include "bang.h"




//# define D(x)  do {printf(__FILE__ ":%d:  ", __LINE__); printf x; fflush(stdout);} while (0)
# define D(x)  ((void) 0)

//# define DN(x)  do {printf(__FILE__ ":%d:  ", __LINE__); printf x; fflush(stdout);} while (0)
# define DN(x)  ((void) 0)


static CompPlugin*   loadPlugin   (char *path);
static Bool          initPlugin   (CompPlugin *p);
static void          finiPlugin   (CompPlugin *p);


//CompPlugin *plugins = 0;

static CompPlugin *
loadPlugin (char *path)
{
D(("BEGIN loadPlugin: %s\n", path));
   CompPlugin *p;
   char       *error;
    
   if (!path)
      return NULL;
   
   p = E_NEW(CompPlugin, 1);
   if (!p)
      return NULL;

   p->dlhand  = 0;
   p->vTable  = 0;

   p->dlhand = dlopen (path, RTLD_LAZY);
   error = dlerror ();
   if (error)
   {
      fprintf (stderr, "%s: dlsym: %s\n", bang->programName, error);
      free (p);
      p = NULL;
   }
   if (p->dlhand)
   {
      PluginGetInfoProc getInfo;

      dlerror ();
      getInfo = (PluginGetInfoProc) dlsym (p->dlhand, "getCompPluginInfo");

      error = dlerror ();
      if (error)
      {
         fprintf (stderr, "%s: Failed to lookup getCompPluginInfo in '%s' "
                  "plugin\n", bang->programName, path);

         dlclose (p->dlhand);
         free (p);
         p = NULL;
      }
      else
      {
         p->vTable = (*getInfo) ();
         if (!p->vTable)
         {
            fprintf (stderr, "%s: Couldn't get vtable from '%s' plugin\n",
                     bang->programName, path);

            dlclose (p->dlhand);
            free (p);
            p = NULL;
         }
      }
   }

   if (p)
   {
      bang->plugins = eina_list_append(bang->plugins, p);

      if (!initPlugin (p))
      {
         fprintf (stderr, "%s: Couldn't activate plugin '%s'\n", bang->programName,
                  p->vTable->name);
         bang->plugins = eina_list_remove(bang->plugins, p);
         
         return NULL;
      }
   }
    
   D(("END   loadPlugin: %s\n",path));
   return p;
}

static Bool
initPlugin (CompPlugin *p)
{
D(("BEGIN\n"));
   CompDisplay *d = bang->displays->data;
   int         failed = 0;

   if (!(*p->vTable->init) (p))
   {
      fprintf (stderr, "%s: InitPlugin '%s' failed\n", bang->programName,
         p->vTable->name);
      return FALSE;
   }

   if (d)
   {
      if ((*d->initPluginForDisplay) (p, d))
      {
         CompScreen *s;
         //CompScreen *failedScreen = d->screensL->data;TODO DAVE replace this
         Eina_List *l;
         for (l = d->screens; l; l = l->next)
         {
            s = l->data;
            if (!p->vTable->initScreen || (*s->initPluginForScreen) (p, s))
            {
               CompWindow *w/*, *failedWindow = s->windows*/;
               Eina_List *ll;
               for (ll = s->windows;ll ;ll = ll->next)
               {
                  w = ll->data;
                  if (p->vTable->initWindow &&
                     !(*p->vTable->initWindow) (p, w))
                  {
                     fprintf (stderr, "%s: Plugin '%s':initWindow "
                        "failed\n", bang->programName, p->vTable->name);
                     //failedWindow = w;
                     failed = 1;
                     break;
                  }
               }
   
               for (ll = s->windows;ll ;ll = ll->next)
               {
                  w = l->data;
                  if (p->vTable->finiWindow)
                     (*p->vTable->finiWindow) (p, w);
               }
            }
            else
            {
               fprintf (stderr, "%s: Plugin '%s':initScreen failed\n",
                  bang->programName, p->vTable->name);
               //failedScreen = s;
               failed = 1;
               break;
            }
         }

         //for (s = d->screens; s != failedScreen; s = s->next)
         //   (*s->finiPluginForScreen) (p, s);    TODO DAVE fix this
      }
      else
      {
         fprintf (stderr, "%s: Plugin '%s':initDisplay failed\n",
            bang->programName, p->vTable->name);

         failed = 1;
         (*d->finiPluginForDisplay) (p, d);
      }
   }

   if (failed)
   {
      (*p->vTable->fini) (p);
      return FALSE;
   }

   return TRUE;
}

static void
finiPlugin (CompPlugin *p)
{
   DF(("finiPlugin [name: %s]\n", p->vTable->name));
   CompDisplay *d = bang->displays->data;
   CompScreen  *s;
   Eina_List *l;
   if (d)
   {
      for (l = d->screens;l ;l = l->next)
      {
         s = l->data;
         
         if (p->vTable->finiWindow)
         {
            Eina_List *ll;
            for (ll = s->windows; ll; ll = ll->next)
               (*p->vTable->finiWindow) (p, (CompWindow*)ll->data);
         }

         (*s->finiPluginForScreen) (p, s);
      }

      (*d->finiPluginForDisplay) (p, d);
   }
   
   if (p->vTable->fini)
      (*p->vTable->fini) (p);
   
   DF(("END finiPlugin\n"));
}

void
screenInitPlugins (CompScreen *s)
{
D((" screenInitPlugins (CompScreen *s)\n"));
   CompPlugin *p;
   Eina_List *l;
   for (l = bang->plugins;l ;l = l->next)
 //  for (p = plugins; p; p = p->next)
   {
      p = l->data;
      if (p->vTable->initScreen)
         (*s->initPluginForScreen) (p, s);
   }
}

void
screenFiniPlugins (CompScreen *s)
{
D(("BEGIN\n"));
   CompPlugin *p;
   Eina_List *l;
   for (l = bang->plugins;l ;l = l->next)
  // for (p = plugins; p; p = p->next)
   {
      p = l->data;
      if (p->vTable->finiScreen)
         (*s->finiPluginForScreen) (p, s);
   }
}

void
windowInitPlugins (CompWindow *w)
{
D((" windowInitPlugins (CompWindow *w)\n"));
   CompPlugin *p;
   Eina_List *l;
   for (l = bang->plugins;l ;l = l->next)
   //for (p = plugins; p; p = p->next)
   {
      p = l->data;
      if (p->vTable->initWindow)
         (*p->vTable->initWindow) (p, w);
   }
}

void
windowFiniPlugins (CompWindow *w)
{
D(("BEGIN\n"));
   CompPlugin *p;
   Eina_List *l;
   for (l = bang->plugins;l ;l = l->next)
   //for (p = plugins; p; p = p->next)
   {
      p = l->data;
      if (p->vTable->finiWindow)
         (*p->vTable->finiWindow) (p, w);
   }
}

int
loadPlugins (char *path)
{
   D((("*** Loading plugins from dir: %s\n"), path));
   Ecore_List *files;
   char       *filename;
   char       buf[4096];
   int        num = 0;
   
   files = ecore_file_ls(path);
   if (!files) return 0;
   
   while ((filename = ecore_list_next(files)))
   {
      if (!ecore_str_has_suffix(filename, ".so")) 
         continue;
      
      snprintf(buf, sizeof(buf), "%s/%s", path, filename);
      if (ecore_file_can_read(buf) && loadPlugin(buf))
         num++;
   }
   D((("*** Loaded %d plugins.\n"), num));
   return num;
}


int
unloadPlugins (void)
{
   CompPlugin *p;

   int num = 0;
   char       *error;
   
   while (bang->plugins)
   {
      p = bang->plugins->data;
      finiPlugin(p);
      dlclose (p->dlhand);
      error = dlerror ();
      if (error)
      {
         fprintf (stderr, "%s: dlsym: %s\n", bang->programName, error);
         free (p);
         p = NULL;
      }
      E_FREE(p);
      num++;
      bang->plugins = eina_list_remove_list(bang->plugins, bang->plugins);
   }
   
   return num;
}

